/*-
 * Copyright (c) 2009 Jonathan Anderson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <libuserangel.h>

#include "Exception.h"
#include "UserAngel.h"

using namespace userAngel;


UserAngel::UserAngel(int socket)
{
	set(socket);
}


int UserAngel::find()
{
	int fd = ua_find();
	if(fd < 0) throw CError("ua_find()");
	
	sock = fd;
	return sock;
}


void UserAngel::set(int socket)
{
	if(socket < 0) throw Exception(QString("Invalid descriptor %1").arg(socket));
	sock = socket;
}


void UserAngel::ping() const
{
	if(ua_ping() < 0) throw CError("ua_ping()");
}


QString UserAngel::cacheFD(QString name, bool longLasting) const
{
	char *capname;
	int ret = ua_cache_fd(sock, name.toStdString().c_str(), &capname, longLasting);

	if(ret < 0)
	{
		free(capname);
		throw CError("ua_cache_fd()");
	}

	QString result(capname);
	free(capname);

	return result;
}

int UserAngel::retrieve(QString token) const
{
	int fd = ua_retrieve_fd(token.toStdString().c_str());
	if(fd < 0) throw CError("ua_retrieve_fd()");

	return fd;
}

/*
QMap<QString,int> UserAngel::powerbox(struct ua_powerbox_options *options);

int UserAngel::access(QString path, int mode);
int UserAngel::stat(QString path, struct stat *s);
int UserAngel::open(QString path, int flags, ...);
int UserAngel::ropen(QString path, int flags, cap_rights_t rights, int umask);

FILE* UserAngel::fopen(QString path, QString mode);
DIR* UserAngel::opendir(QString filename);
*/

void UserAngel::send(int32_t val, QList<int> fds) const
{
	struct ua_datum *d = marshall(val);
	send(d, fds);
}

void UserAngel::send(int64_t val, QList<int> fds) const
{
	struct ua_datum *d = marshall(val);
	send(d, fds);
}

void UserAngel::send(QByteArray val, QList<int> fds) const
{
	struct ua_datum *d = marshall(val);
	send(d, fds);
}

void UserAngel::send(QString val, QList<int> fds) const
{
	struct ua_datum *d = marshall(val);
	send(d, fds);
}

void UserAngel::send(struct ua_powerbox_options* val) const
{
	struct ua_datum *d = marshall(val);
	send(d);
}

void UserAngel::sendError(QString message, int errnum) const
{
	struct ua_datum *d = marshallError(message, errnum);
	send(d);
}

void UserAngel::send(struct ua_datum *d, QList<int> fds) const
{
	size_t fdcount = fds.size();
	int *fdarray = (fdcount > 0) ? new int[fdcount] : NULL;
	for(size_t i = 0; i < fds.size(); i++) fdarray[i] = fds.at(i);

	int bytes = ua_send(sock, d, fdarray, fdcount);
	delete [] fdarray;

	if(bytes < 0) throw CError("ua_send()");
}


UserAngel::Datum UserAngel::recv(size_t maxFDs) const
{
	size_t len = maxFDs;
	int *fds = (len > 0) ? new int[len] : NULL;

	struct ua_datum* d = ua_recv(sock, fds, &len);
	if(!d)
	{
		if(fds) delete [] fds;
		throw CError("ua_recv()");
	}

	Datum datum(d, fds, len);
	if(fds) delete [] fds;

	return datum;
}

/*
// low-level API
void UserAngel::send(struct ua_datum *d, int fds[], size_t fdlen);



struct ua_datum* UserAngel::ua_marshall_int(int32_t value);
	if(!d) throw CError("ua_marshall_int()");
*/

struct ua_datum* UserAngel::marshall(int32_t val) const
{
	struct ua_datum *d = ua_marshall_int(val);
	if(!d) throw CError("ua_marshall_int()");

	return d;
}

struct ua_datum* UserAngel::marshall(int64_t val) const
{
	struct ua_datum *d = ua_marshall_long(val);
	if(!d) throw CError("ua_marshall_long()");

	return d;
}

struct ua_datum* UserAngel::marshall(QByteArray val) const
{
	struct ua_datum *d = ua_marshall_bytes(val.constData(), val.size());
	if(!d) throw CError("ua_marshall_bytes()");

	return d;
}

struct ua_datum* UserAngel::marshall(QString val) const
{
	struct ua_datum *d = ua_marshall_string(val.toStdString().c_str(), val.size());
	if(!d) throw CError("ua_marshall_string()");

	return d;
}

struct ua_datum* UserAngel::marshallError(QString message, int errnum) const
{
	struct ua_datum *d =
		ua_marshall_error(errnum, message.toStdString().c_str(), message.size());

	if(!d) throw CError("ua_marshall_error()");

	return d;
}

struct ua_datum*
	UserAngel::marshall(const struct ua_powerbox_options *options) const
{
	struct ua_datum *d = ua_marshall_powerbox(options);
	if(!d) throw CError("ua_marshall_powerbox()");

	return d;
}







UserAngel::Datum::Datum(struct ua_datum* d, int fds[], size_t fdcount)
	: _datum(d)
{
	for(size_t i = 0; i < fdcount; i++) _fds.append(fds[i]);
}

int32_t UserAngel::Datum::toInt() const
{
	int32_t val;

	int bytes = ua_unmarshall_int(_datum.data(), &val);
	if(bytes < 0) throw CError("ua_unmarshall_int()");

	return val;
}

int64_t UserAngel::Datum::toLong() const
{
	int64_t val;

	int bytes = ua_unmarshall_long(_datum.data(), &val);
	if(bytes < 0) throw CError("ua_unmarshall_long()");

	return val;
}

QByteArray UserAngel::Datum::toBytes() const
{
	size_t len = 128;
	char *buf = new char[len];

	int bytes = ua_unmarshall_bytes(_datum.data(), buf, &len);
	if(bytes < 0)
	{
		if(errno == EOVERFLOW)
		{
			// buffer too small; len has been changed to required size
			delete [] buf;
			buf = new char[len];

			bytes = ua_unmarshall_bytes(_datum.data(), buf, &len);
		}

		if(bytes < 0)
		{
			delete [] buf;
			throw CError("ua_unmarshall_bytes()");
		}
	}

	QByteArray result(buf, len);
	delete [] buf;

	return result;
}

QString UserAngel::Datum::toString() const
{
	size_t len = 128;
	char *buf = new char[len];

	int bytes = ua_unmarshall_string(_datum.data(), buf, &len);
	if(bytes < 0)
	{
		if(errno == EOVERFLOW)
		{
			// buffer too small; len has been changed to required size
			delete [] buf;
			buf = new char[len];

			bytes = ua_unmarshall_string(_datum.data(), buf, &len);
		}

		if(bytes < 0)
		{
			delete [] buf;
			throw CError("ua_unmarshall_string()");
		}
	}

	QString result(QByteArray(buf, len));
	delete [] buf;

	return result;
}

struct ua_powerbox_options UserAngel::Datum::toPowerbox() const
{
	struct ua_powerbox_options options;
	if(ua_unmarshall_powerbox(_datum.data(), &options) < 0)
		throw CError("ua_unmarshall_powerbox()");

	return options;
}

/*
QString UserAngel::Datum::toString()
{
}

struct ua_powerbox_options UserAngel::unmarshall_powerbox(const struct ua_datum *d)
{
}
*/

QList<int> UserAngel::Datum::fds() const { return _fds; }

